#include "stdafx.h"
#include "typedefs.h"
#include "errordef.h"
#include "../TracerDLL.h"

#include <stdlib.h>
#include <string.h>

#include <assert.h>

#include "xmlscan.h"

#define LEGAL_NAME_1ST_CHAR(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')
#define LEGAL_NAME_CHAR(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || \
                              c == '_' || c == '-' ||c == '.' || c == ':' || \
                              (c >= '0' && c <= '9'))
#define WHITE_SPACE(c) (c == ' ' || c == '\t' || c == '\r' || c == '\n')

#define LESS_THAN_ENTITY            "&lt;"
#define AMPERSAND_ENTITY            "&amp;"
#define GREATER_THAN_ENTITY         "&gt;"
#define QUOTE_ENTITY                "&quot;"
#define APOSTROPHE_ENITY            "&apos;" 
#define NBSPACE_ENTITY              "&nbsp;"  


/////////////////////////////////////////////////////////////////
// CFileStream
/////////////////////////////////////////////////////////////////
CFileStream::CFileStream (int32 nBufSize)
{
   assert (nBufSize > 0);
   
   m_hFile     = NULL;
   m_nSize     = nBufSize;
   m_pBuffer   = (char*) malloc (nBufSize);
   DBG_NewMem (m_pBuffer, "CFileStream::CFileStream, m_pBuffer malloced.");

   assert (m_pBuffer);
   m_nPrevLine = m_nLine = 1;
   m_nPrevCol = m_nCol = 0;
   
   m_nCurrent  = m_nTotalChars = 0;
}

CFileStream::~CFileStream ()
{
   if (m_hFile)
      CloseFile ();

   if (m_pBuffer) {
      free (m_pBuffer);
      DBG_DelMem (m_pBuffer, "CFileStream::~CFileStream m_pBuffer freed.");
   }
}

int32 CFileStream::OpenFile (char* pszFilename)
{
   assert (m_hFile == NULL);
   assert (pszFilename);
   assert (m_nSize > 0);
   
   m_hFile = fopen (pszFilename, "rt");
   if (!m_hFile)
      return E_ERRORNO;

   m_nTotalChars = fread (m_pBuffer, 1, m_nSize, m_hFile);
   
   m_nCurrent  = 0;   
   m_nLine     = 1;
   m_nCol      = 0;
   
   return 0;
}

void CFileStream::CloseFile ()
{
   assert (m_hFile);
   
   fclose (m_hFile);
   m_hFile = NULL;
}

int32 CFileStream::GetChar (char* pChar)
{
   assert (m_nCurrent >= 0);
   assert (m_nSize >= 0);
   assert (m_nTotalChars >= 0);
   assert (m_hFile);
   assert (pChar);
   
   if (m_nCurrent < m_nTotalChars) {
      return GetCharFromBuffer (pChar);
   }
   else {
      /* Should read the next piece of the file. */
      m_nTotalChars = fread (m_pBuffer, 1, m_nSize, m_hFile);
      m_nCurrent = 0;
      if (m_nTotalChars == 0)
         return E_EOF;
      return GetCharFromBuffer (pChar);
   }
}

int32 CFileStream::PutChar (char c)
{
   assert (m_pBuffer);
   assert (m_nCurrent > 0);
   
   assert (m_nPrevLine != m_nLine || m_nPrevCol != m_nCol);
   
   m_nCurrent --;
   m_pBuffer[m_nCurrent] = c;
   
   m_nLine = m_nPrevLine;
   m_nCol = m_nPrevCol;
   return 0;
}

int32 CFileStream::GetCharFromBuffer (char* pChar)
{
   assert (m_nCurrent >= 0);
   assert (m_nSize >= 0);
   assert (m_nTotalChars >= 0);
   assert (m_hFile);
   assert (pChar);
   assert (m_nCurrent < m_nTotalChars);

   *pChar = m_pBuffer[m_nCurrent];
   m_nCurrent ++;
   
   m_nPrevLine = m_nLine;
   m_nPrevCol  = m_nCol;
   
   if (*pChar == LF_CHAR) {
      m_nLine ++;
      m_nCol = 0;
   } else {
      m_nCol ++;
   }
   return 0;
}

/////////////////////////////////////////////////////////////////
// CXMLSCanner
/////////////////////////////////////////////////////////////////
CXMLScanner::CXMLScanner (int32 nBufSize)
{
   assert (nBufSize > 0);
  
   m_nSize = nBufSize;
   m_pStream = NULL;
   m_pToken = (char*) malloc (m_nSize);
   DBG_NewMem(m_pToken, "CXMLScanner::CXMLScanner m_pToken malloced.");

   m_nCol = m_nLine = -1;
   assert (m_pToken);  
   m_nIdx = 0;
}

CXMLScanner::~CXMLScanner ()
{
   if (m_pToken) {
      free (m_pToken);
      DBG_DelMem(m_pToken, "CXMLScanner::~CXMLScanner, m_pToken freed.");
   }
   
   if (m_pStream) {
      delete m_pStream;
      DBG_DelMem(m_pStream, "CXMLScanner::~CXMLScanner, m_pStream freed.");
   }
}

int32 CXMLScanner::OpenFile (char* szFilename)
{
   assert (szFilename);
   assert (m_pStream == NULL);
   assert (m_nSize > 0);
   
   m_pStream = new CFileStream (m_nSize);
   DBG_NewMem (m_pStream, "CXMLScanner::OpenFile, file stream created.");
   
   if (!m_pStream) {
      return E_INSUFFICIENT_MEMORY;
   }
      
   return m_pStream->OpenFile (szFilename);
}

void CXMLScanner::CloseFile ()
{
   assert (m_pStream );
   
   m_pStream->CloseFile ();
   delete m_pStream;
   DBG_DelMem (m_pStream, "CXMLScanner::ClosenFile, file stream deleted.");
   m_pStream = NULL;
   return; 
}

int32 CXMLScanner::GetToken ()
{
   int32    nState = STATE_START;
   char     c;
   int32    nTokenType;
   
   m_nIdx = 0;
   m_nCol = m_nLine = -1;
   
   assert (m_pStream);
      
   while (nState != STATE_ACCEPT) {
      int nRet = m_pStream->GetChar (&c);
      if (nRet < 0)
         return TOKEN_EOF; // EOF or error.
      
      switch (nState) {
      case STATE_START:
         if ( LEGAL_NAME_1ST_CHAR(c) ) {
            // Starts an XML name. store it to m_pToken.
            nState = STATE_NAME;
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
         } else if (c == '<') {
            nState = STATE_LEFT_BRACKET;      
         } else if (c == '\'') {
            assert (m_nCol < 0 && m_nLine < 0 );
            
            nState = STATE_SINGLE_QUOTE;
            m_nLine = m_pStream->m_nLine;
            m_nCol = m_pStream->m_nCol;
         } else if (c == '"') {
            assert (m_nCol < 0 && m_nLine < 0 );
            
            nState = STATE_DOUBLE_QUOTE;
            m_nLine = m_pStream->m_nLine;
            m_nCol = m_pStream->m_nCol;
         } else if (c == '/') {
           nState = STATE_BACKSLASH; 
         } else if (c == '>') {
            nState = STATE_ACCEPT;
            nTokenType = '>';
         } else if (c == '=') {
            nState = STATE_ACCEPT;
            nTokenType = '=';
         } else if (WHITE_SPACE(c)) {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_WHITE_SPACE ;
         } else {
            return E_BAD_TOKEN;
         }
         break;
      
      case STATE_NAME:
         if ( LEGAL_NAME_CHAR(c) ) {
            // Store it to m_pToken. No state change.
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
         } else if (WHITE_SPACE (c)) {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_NAME;
         } else if (c == '=') {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_NAME;
            m_pStream->PutChar ('=');
         } else if (c == '>') {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_NAME;
            m_pStream->PutChar ('>');
         } else {
            return E_INVALID_CHAR_IN_NAME;
         }
         break;
      
      case STATE_SINGLE_QUOTE:
         // Store the character, until another '.
         if (c != '\'') {
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
         } else {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_VALUE;
         }   
         break;
      
      case STATE_DOUBLE_QUOTE:
         // Store the character, until another ".
         if (c != '"') {
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
         } else {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_VALUE;
         }   
         break;
      
      case STATE_LEFT_BRACKET:
         if (c == '!') {
            nState = STATE_EXCLAIM;
         } else if (c == '?') {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_PI_BEGIN;
         } else if (c == '/') {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_END_TAG_BEGIN;
         } else {
            // Put back the chacater.
            m_pStream->PutChar (c);
            nState = STATE_ACCEPT;
            nTokenType = '<';
         }
         break;
      
      case STATE_EXCLAIM:
         if (c == '-') {
            nState = STATE_1ST_MINUS;
         } else if (c == '[') {
            nState = STATE_1ST_LEFT_SQUARE;
         } else if (c == 'D') {
            nState = STATE_DOCTYPE_D;
         } else {
            return E_INVALID_DECL_NAME;
         } 
         break;
      
      case STATE_1ST_MINUS:
         if (c == '-') {
            nState = STATE_2ND_MINUS;
         } else {
            return E_INVALID_COMMENT;
         }
         break;
      
      case STATE_2ND_MINUS:
         if (c == '-') {
            nState = STATE_3RD_MINUS;
         }
         break;
         
      case STATE_3RD_MINUS:
         if (c == '-') {
            nState = STATE_4TH_MINUS;
         } else {
            nState = STATE_2ND_MINUS;
         }
         break;
         
      case STATE_4TH_MINUS:
         if (c == '>') {
            nState = STATE_ACCEPT,
            nTokenType = TOKEN_COMMENT;
         } else {
            return E_INVALID_COMMENT;
         }
         break;
         
      case STATE_1ST_LEFT_SQUARE:
         if (c == 'C') {
            nState = STATE_C;
         } else {
            return E_INVALID_CDATA;
         }
         break;
         
      case STATE_C:
         if (c == 'D') {
            nState = STATE_D;        
         } else {
            return E_INVALID_CDATA;
         }
         break;
      
      case STATE_D:
         if (c == 'A') {
            nState = STATE_1ST_A;        
         } else {
            return E_INVALID_CDATA;
         }
         break;
         
      case STATE_1ST_A:
         if (c == 'T') {
            nState = STATE_T;        
         } else {
            return E_INVALID_CDATA;
         }
         break;
         
      case STATE_T:
         if (c == 'A') {
            nState = STATE_2ND_A;        
         } else {
            return E_INVALID_CDATA;
         }
         break;
         
      case STATE_2ND_A:
         if (c == '[') {
            nState = STATE_2ND_LEFT_SQUARE;        
         } else {
            return E_INVALID_CDATA;
         }
         break;
         
      case STATE_2ND_LEFT_SQUARE:
         // Store it to m_pToken.
         if (m_nIdx < m_nSize) {
            m_pToken[m_nIdx] = c;
            m_nIdx++;
         } else {
            return E_BUFFER_OVERFLOWN;
         }
         if (c == ']') {
            nState = STATE_1ST_RIGHT_SQUARE;        
         } 
         break;
         
      case STATE_1ST_RIGHT_SQUARE:
         // Store it to m_pToken. 
         if (m_nIdx < m_nSize) {
            m_pToken[m_nIdx] = c;
            m_nIdx++;
         } else {
            return E_BUFFER_OVERFLOWN;
         }
         if (c == ']') {
            nState = STATE_2ND_RIGHT_SQUARE;        
         } else {
            nState = STATE_2ND_LEFT_SQUARE;
         }
         break;
         
      case STATE_2ND_RIGHT_SQUARE:
         // Store it to m_pToken. 
         if (m_nIdx < m_nSize) {
            m_pToken[m_nIdx] = c;
            m_nIdx++;
         } else {
            return E_BUFFER_OVERFLOWN;
         }
         if (c == '>') {
            nState = STATE_ACCEPT;
            m_nIdx -= 2;
            nTokenType = TOKEN_CDATA_SECTION;        
         } else {
            nState = STATE_2ND_LEFT_SQUARE;
         }
         break;
      
      case STATE_BACKSLASH:
         if (c == '>') {
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_ENDING;
         } else {
            return E_RIGHT_BRACKET_EXPECTED;   
         }
         break;
         
         case STATE_DOCTYPE_D:
            if (c == 'O') {
               nState = STATE_DOCTYPE_O;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_O:
            if (c == 'C') {
               nState = STATE_DOCTYPE_C;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_C:
            if (c == 'T') {
               nState = STATE_DOCTYPE_T;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_T:
            if (c == 'Y') {
               nState = STATE_DOCTYPE_Y;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_Y:
            if (c == 'P') {
               nState = STATE_DOCTYPE_P;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_P:
            if (c == 'E') {
               nState = STATE_DOCTYPE_E;
            } else {
               return E_INVALID_DECL_NAME;
            }
            break;
            
         case STATE_DOCTYPE_E:
            if (c == '>') {
               nState = STATE_ACCEPT;
               nTokenType = TOKEN_DOCTYPE;
            }
            break;
         
      default:
         assert (0);
      }   
   }   
   
   // add the null terminator the m_pToken
   if (m_nIdx < m_nSize) {
      m_pToken[m_nIdx] = '\0';
   } else {
      return E_BUFFER_OVERFLOWN;
   }
   
   if (nTokenType == TOKEN_VALUE) 
      return ValueStringPostProcess (m_pToken);
      
   m_nCol = m_nLine = -1;
   return nTokenType;
}
   
int32 CXMLScanner::GetPI ()
{
   int32       nState = STATE_START;
   char        c;
   int32       nTokenType;
   
   m_nIdx = 0;
   m_nCol = m_nLine = -1;
   
   assert (m_pStream);
      
   while (nState != STATE_ACCEPT) {
      int nRet = m_pStream->GetChar (&c);
      if (nRet < 0)
         return TOKEN_EOF; // EOF or error.
      
      switch (nState) {
      case STATE_START:
         // Store it to m_pToken. 
         if (m_nIdx < m_nSize) {
            m_pToken[m_nIdx] = c;
            m_nIdx++;
         } else {
            return E_BUFFER_OVERFLOWN;
         }
         if (c == '?') {
            nState = STATE_QUESTION_MARK;
         }
         break;
         
      case STATE_QUESTION_MARK:
         if (c == '>') {
            m_nIdx --;  // Remove the '?'
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_PI;
         } else {
            // Store it to m_pToken. 
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
            nState = STATE_START;
         }
         break;
      
      default: 
         assert (0);
      }
   }
   
   // add the null terminator the m_pToken
   if (m_nIdx < m_nSize) {
      m_pToken[m_nIdx] = '\0';
   } else {
      return E_BUFFER_OVERFLOWN;
   }

   m_nCol = m_nLine = -1;
   return nTokenType;
}

int32 CXMLScanner::GetCDATA ()
{
   int32       nState = STATE_START;
   char        c;
   int32       nTokenType;
   
   m_nIdx   = 0;
   m_nCol   = m_pStream->m_nCol;
   m_nLine  = m_pStream->m_nLine;
   
   assert (m_pStream);
      
   while (nState != STATE_ACCEPT) {
      int nRet = m_pStream->GetChar (&c);
      if (nRet < 0)
         return TOKEN_EOF; // EOF or error.
      
      switch (nState) {
      case STATE_START:
         if (c == '<') {
            m_pStream->PutChar ('<');
            nState = STATE_ACCEPT;
            nTokenType = TOKEN_CDATA;
         } else {
            // Store it to m_pToken. 
            if (m_nIdx < m_nSize) {
               m_pToken[m_nIdx] = c;
               m_nIdx++;
            } else {
               return E_BUFFER_OVERFLOWN;
            }
         }
         break;
         
      default:   
         assert (0);
      }
   }
   
   // add the null terminator the m_pToken
   if (m_nIdx < m_nSize) {
      m_pToken[m_nIdx] = '\0';
   } else {
      return E_BUFFER_OVERFLOWN;
   }
   
   return CDATAStringPostProcess (m_pToken);
}

int32 CXMLScanner::GetLineNo ()
{
   assert (m_pStream);
   if (m_nLine > 0)
      return m_nLine;
      
   return m_pStream->m_nLine;
}

int32 CXMLScanner::GetColNo ()
{
   assert (m_pStream);
   if (m_nCol > 0)
      return m_nCol;
      
   return m_pStream->m_nCol;
}

int32 CXMLScanner::ValueStringPostProcess (char* szString)
{
   int32    i;
   int32    j;
   char     c;
   int32    nRet;
   int32    nAmpPos = -1;
   
   i = j = 0;
   
   while (szString[i] != '\0') {
      c = szString [i];
      if (c == LF_CHAR) {
         m_nLine ++;
         m_nCol = 0;
      } else {
         m_nCol ++;
      }
      
      if (WHITE_SPACE(c)) {
         // Leading spaces are ignored.
         if (j != 0) {
            // Successive spaces are combined.
            if (szString[j-1] != ' ') {
               szString[j] = ' ';
               j++;
            }
         }
      } else {
         szString[j] = c;
         nRet = CheckReferenceEntities (szString, i, &j, &nAmpPos);
         j++;
         if (nRet != 0)
            return nRet;
      }
      i++;
   }
   
   szString[j] = '\0';
   if (j > 0 && szString[j-1] == ' ') {
      szString[j-1] = '\0';
   }
   
   if (nAmpPos != -1)
      return E_SEMICOLON_WANTED;
      
   return TOKEN_VALUE;
}

int32 CXMLScanner::CDATAStringPostProcess (char* szString)
{
   int32    i;
   int32    j;
   char     c;
   int32    nRet;
   int32    nAmpPos = -1;
   
   i = j = 0;
   
   while (szString[i] != '\0') {
      c = szString [i];
      if (c == LF_CHAR) {
         m_nLine ++;
         m_nCol = 0;
      } else {
         m_nCol ++;
      }
      
      szString[j] = c;
      nRet = CheckReferenceEntities (szString, i, &j, &nAmpPos);
      if (nRet != 0)
         return nRet;
      j++;
      i++;
   }
   
   szString[j] = '\0';
   if (nAmpPos != -1)
      return E_SEMICOLON_WANTED;

   return TOKEN_CDATA;
}

int32 CXMLScanner::CheckReferenceEntities (char* szString, int nSrc, int* pnDest, int* pnAmpPos)
{
   char     c;
   
   assert (szString);
   assert (nSrc >= 0);
   assert (pnDest);
   assert (*pnDest >= 0);
   assert (pnAmpPos);
   
   c = szString[nSrc];
   
   if (c == '&') {
      if (*pnAmpPos != -1) {
         // There is an open & 
         if (*pnAmpPos == *pnDest -1)
            return E_INVALID_CHAR_IN_NAME;
         else
            return E_SEMICOLON_WANTED;
      } else {
            *pnAmpPos = *pnDest;
      }
   } else if (WHITE_SPACE(c) && *pnAmpPos != -1) {
      return E_SEMICOLON_WANTED;
   } else if (c == ';' && *pnAmpPos != -1) {
      char* pTmp = szString + *pnAmpPos;
      if (strncmp (pTmp, LESS_THAN_ENTITY, strlen (LESS_THAN_ENTITY)) == 0) 
         szString[*pnAmpPos] = '<';
      else if (strncmp (pTmp, AMPERSAND_ENTITY, strlen (AMPERSAND_ENTITY)) == 0)
         szString[*pnAmpPos] = '&';
      else if (strncmp (pTmp, GREATER_THAN_ENTITY, strlen (GREATER_THAN_ENTITY)) == 0)
         szString[*pnAmpPos] = '>';
      else if (strncmp (pTmp, QUOTE_ENTITY, strlen (QUOTE_ENTITY)) == 0)
         szString[*pnAmpPos] = '"';
      else if (strncmp (pTmp, APOSTROPHE_ENITY, strlen (APOSTROPHE_ENITY)) == 0)
         szString[*pnAmpPos] = '\'';
      else if (strncmp (pTmp, NBSPACE_ENTITY, strlen (NBSPACE_ENTITY)) == 0) 
         szString[*pnAmpPos] = ' ';
      else
         return E_UNDEFINED_ENTITY;
      *pnDest = *pnAmpPos;
      *pnAmpPos = -1;
   }
   return 0;
}

